package com.cosc716.application.gui.components;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Hashtable;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SliderAndSpinnerComboPanel extends JPanel
	implements ChangeListener, MouseListener, ActionListener, KeyListener {
	
	private static final long serialVersionUID = 2490421978736397686L;
	
	private JLabel label;
	private JSlider slider;
	private JSpinner spinner;
	private String panelName;
		
	private int value;
	
	private ArrayList<ChangeListener> registeredChangeListeners;

	public SliderAndSpinnerComboPanel(String panelName, String labelText,
										int min, int max, int startValue) {
		this.panelName = panelName;
		
		label = new JLabel(labelText);
		slider = new JSlider(JSlider.HORIZONTAL, min, max, startValue);
		spinner = new JSpinner();
		
		Hashtable<Integer, JLabel> labelTable = new Hashtable<Integer, JLabel>();
		labelTable.put(min, new JLabel(Integer.toString(min)));
		labelTable.put(((max-min)/2)+min, new JLabel(Integer.toString(((max-min)/2)+min)));
		labelTable.put(max, new JLabel(Integer.toString(max)));
		slider.setLabelTable(labelTable);
		slider.setPaintLabels(true);
		slider.addChangeListener(this);
		
		
		spinner.setValue(startValue);
		SpinnerModel spinnerModel = new SpinnerNumberModel(
											startValue,
											min,
											max,
											1);
		spinner.setModel(spinnerModel);
		((JSpinner.DefaultEditor)spinner.getEditor())
				.getTextField().setColumns(Integer.toString(max).length());
		((JSpinner.DefaultEditor)spinner.getEditor())
				.getTextField().addActionListener(this);
		((JSpinner.DefaultEditor)spinner.getEditor())
			.getTextField().addKeyListener(this);
		addSpinnerButtonListeners();
		
		this.setLayout(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.weightx = 1.0;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		
		gbc.gridx = gbc.gridy = 0;
		this.add(label, gbc);
		
		gbc.gridx = 0;
		gbc.gridy = 1;
		this.add(slider, gbc);
		
		gbc.gridx = 1;
		gbc.gridy = 1;
		gbc.fill = GridBagConstraints.NONE;
		this.add(spinner, gbc);
		
		value = startValue;
		
	}

	private void addSpinnerButtonListeners() {
		// Hacky way to add listeners to the spinners
		// buttons so that they only fire an event when
		// the user releases them
		for(Component component : spinner.getComponents()) {
			if(component instanceof JButton) {
				((JButton)component).addMouseListener(this);
			}
		}
	}
	
	public String getPanelName() {
		return panelName;
	}
	
	public int getValue() {
		return value;
	}

	@Override
	public void stateChanged(ChangeEvent e) {
		if(e.getSource() instanceof JSlider) {
			JSlider source = (JSlider)e.getSource();
			if(!source.getValueIsAdjusting()) {
				spinner.setValue(source.getValue());

				value = slider.getValue();
				
				fireStateChanged();
			}
		}
		else if(e.getSource() instanceof JSpinner) {
			updateSliderFromSpinner();
			fireStateChanged();
		}
	}
	
	public void addChangeListener(ChangeListener newChangeListener) {
		if(registeredChangeListeners == null) {
			registeredChangeListeners = new ArrayList<ChangeListener>();
		}
		
		if(!registeredChangeListeners.contains(newChangeListener)) {
			registeredChangeListeners.add(newChangeListener);
		}
	}
	
	private void fireStateChanged() {
		for(ChangeListener changeListener : registeredChangeListeners) {
			changeListener.stateChanged(new ChangeEvent(this));
		}
	}

	@Override
	public void mouseClicked(MouseEvent e) { }

	@Override
	public void mouseEntered(MouseEvent e) { }

	@Override
	public void mouseExited(MouseEvent e) { }

	@Override
	public void mousePressed(MouseEvent e) {
		// Update the slider as the user holds the spinner buttons
		updateSliderFromSpinner();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		updateSliderFromSpinner();
		
		// Fire that the state has changed based on the release
		// of a spinner button
		fireStateChanged();
	}
	
	private void updateSliderFromSpinner() {
		slider.setValue(Integer.parseInt(spinner.getValue().toString()));
		value = slider.getValue();
	}

	@Override
	public void keyPressed(KeyEvent e) { }

	@Override
	public void keyReleased(KeyEvent e) { }

	@Override
	public void keyTyped(KeyEvent e) {
		char c = e.getKeyChar();
		if( !Character.isDigit(c) &&
			c != '\b' ) {
			// Not a digit or backspace
			// get rid of it
			e.consume();
		}
	}

	@Override
	public void actionPerformed(ActionEvent arg0) {
		updateSliderFromSpinner();
		fireStateChanged();
	}
}
